package ${projectDefinitionDO.serverProjectDefinitionDO.serverProjectStartClassPackagePathName}.controller;

<#noparse>
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Controller;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.*;
import org.springframework.web.multipart.MultipartFile;
import org.web.base.domain.ViewResultHelper;
import org.web.base.domain.exception.ResultMessageEnum;
import org.web.base.domain.exception.ServiceException;
import org.web.base.domain.helper.ServiceExceptionHelper;
import org.web.base.domain.ResultDO;
import org.web.base.domain.view.ViewResultDTO;
import org.web.base.helper.*;
import org.web.base.helper.excel.*;

import jakarta.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;

/***
 * 只用一个类下载所有实体的Excel文件。 完全继承查询接口提供的功能。
 * 未来的扩展功能：
 * 1. 增加权限控制，数据权限，围绕entityName的权限控制.
 * 2. 控制下载Excel的数量，避免下载过大的文件。
 * 3. 返回记录总数。
 * */
@Controller()
</#noparse>
@RequestMapping("${projectDefinitionDO.serverProjectDefinitionDO.serverProjectClassNameForVariableName}Excel")
<#noparse>
public class ExcelController {
    private static final Logger logger = LogManager.getLogger(ExcelController.class);
</#noparse>

    private static final String QUERY_BASE_PACKAGE = "${projectDefinitionDO.serverProjectDefinitionDO.serverProjectStartClassPackagePathName}.domain.query.";
    private static final String DTO_BASE_PACKAGE = "${projectDefinitionDO.basePackage}.sdk.dto.";
    private static final String DOMAIN_BASE_PACKAGE = "${projectDefinitionDO.serverProjectDefinitionDO.serverProjectStartClassPackagePathName}.domain.";
<#noparse>

    private final BeanFactory beanFactory;

    public ExcelController(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    private static final Gson gson = new Gson();

    /**
     * 未来的扩展功能：
     * 1. 增加权限控制，数据权限，围绕entityName的权限控制.
     * 2. 控制下载Excel的数量，避免下载过大的文件。
     * 3. 返回记录总数。
     */
    @RequestMapping("/downloadExcel")
    @ResponseBody
    public Object downloadExcel(@RequestBody Map<String, Object> params) {

        try {
            ExcelOperator excelOperator = buildExcelOperator(params);
            List<Map<String, String>> attributeLabelMapList = null;
            if (params.get("attributeLabelMapList") != null) {
                String attributeLabelMapListStr = params.get("attributeLabelMapList").toString();
                Gson gson = new Gson();
                Type listType = new TypeToken<List<Map<String, String>>>() {
                }.getType();
                attributeLabelMapList = gson.fromJson(attributeLabelMapListStr, listType);
            }
            checkCommonExcelOperator(beanFactory, excelOperator);
            Class<?> queryClass = Class.forName(excelOperator.getQueryClassName());
            Class<?> dtoClass = Class.forName(excelOperator.getDtoClassName());
            Object queryBean = BeanConvertHelper.convertMapToBean(params, queryClass);

            ResultDO resultDO = getResultDOByQuery(excelOperator, queryBean, beanFactory);
            if (resultDO.isFailed()) {
                throw ServiceExceptionHelper.buildServiceException(resultDO);
            }
            List<OneTask> queryTaskList = new ArrayList<>();
            OneTask oneTask = new OneTask();
            String entityName = dtoClass.getSimpleName().replace("DTO", "");
            oneTask.setTaskMark("Query-" + entityName + "-1");
            oneTask.setQueryObject(queryBean);
            oneTask.setResultDO(resultDO);
            queryTaskList.add(oneTask);
            String fileName = buildDownloadExcelFileName(excelOperator);
            byte[] excelContent = DownloadHelper.exportData2Byte(queryTaskList, attributeLabelMapList, excelOperator, false);
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replace("+", "%20");
            headers.set("Content-Disposition", "attachment; filename*=" + encodedFileName);
            return new ResponseEntity<>(excelContent, headers, HttpStatus.OK);
        } catch (Exception e) {
            logger.error(ServiceExceptionHelper.getExceptionInfo(e));
            return gson.toJson(ViewResultHelper.buildViewResultByServiceException(e, null));
        }
    }


    /***
     * 根据上传的文件下载Excel文件，上传的文件中包含查询条件。
     * 下载文件的sheet名称为 , 由查询条件的rowMark组成。
     * */
    @PostMapping("/downloadExcelByFile")
    @ResponseBody
    public Object downloadExcelByFile(MultipartFile file, ExcelOperator excelOperator) {

        try {
            initExcelOperator(excelOperator);
            checkCommonExcelOperator(beanFactory, excelOperator);
            checkDownloadExcelOperator(beanFactory, excelOperator);

            Class<?> queryClass = Class.forName(excelOperator.getQueryClassName());

            if (file.isEmpty()) {
                throw ServiceExceptionHelper.buildServiceException(ResultMessageEnum.PARAM_EMPTY, "上传文件为空。");
            }
            InputStream inputStream = file.getInputStream();
            // 使用 Apache POI 读取 Excel 文件
            Workbook workbook = new XSSFWorkbook(inputStream);
            inputStream.close(); // 读完立即关闭
            Sheet sheet = workbook.getSheet("DownloadQuery");
            // 遍历 Excel 表格
            Class<?> queryClazz = Class.forName(excelOperator.getQueryClassName());
            List<OneRowDTO> queryList = DownloadHelper.sheetToListMap(sheet, queryClazz);

            Object serviceBean = beanFactory.getBean(excelOperator.getServiceBeanName());
            Method method = serviceBean.getClass().getMethod(excelOperator.getSelectMethodName(), queryClass);
            method.setAccessible(true);

            List<OneTask> queryTaskList = new ArrayList<>();
            Class dtoClass = Class.forName(excelOperator.getDtoClassName());
            int i = 1;
            for (OneRowDTO oneRowDTO : queryList) {
                Object queryBean = BeanConvertHelper.convertMapToBean(oneRowDTO.getRowDataMap(), queryClass);
                ResultDO resultDO = (ResultDO<List<?>>) method.invoke(serviceBean, queryBean);
                OneTask oneTask = new OneTask();
                String entityName = dtoClass.getSimpleName().replace("DTO", "");
                if (StringUtils.isNotBlank(oneRowDTO.getRowMark())) {
                    oneTask.setTaskMark(oneRowDTO.getRowMark());
                } else {
                    oneTask.setTaskMark("Query-" + entityName + "-" + i);
                }
                oneTask.setQueryObject(queryBean);
                oneTask.setResultDO(resultDO);
                queryTaskList.add(oneTask);
                i++;
            }

            byte[] excelContent = DownloadHelper.exportData2Byte(queryTaskList, null, excelOperator, false);

            String fileName = buildDownloadExcelFileName(excelOperator);
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setContentDispositionFormData("attachment", fileName);
            return new ResponseEntity<>(excelContent, headers, HttpStatus.OK);
        } catch (Exception e) {
            if (e.getCause() != null && e.getCause() instanceof Exception) {
                return ViewResultHelper.buildViewResultByServiceException((Exception) e.getCause(), null);
            }
            logger.error(ServiceExceptionHelper.getExceptionInfo(e));
            return ViewResultHelper.buildViewResultByServiceException(e, null);
        }
    }

    @PostMapping("/fileUpload")
    @ResponseBody
    public ViewResultDTO<String> fileUpload(MultipartFile file, @RequestParam Map<String, Object> params,
                                            HttpServletRequest request) {
        ViewResultDTO<String> viewResultDTO = new ViewResultDTO<>(true);

        try {
            ExcelOperator excelOperator = ExcelController.buildExcelOperator(params);
            initExcelOperator(excelOperator);
            checkCommonExcelOperator(beanFactory, excelOperator);
            checkUploadExcelOperator(beanFactory, excelOperator);

            Object bizBean = beanFactory.getBean(excelOperator.getBizClassName());
            String upperEntityName = excelOperator.getEntityName().substring(0, 1).toUpperCase() + excelOperator.getEntityName().substring(1);
            //TODO 推荐用biz层的接口做处理。而非Service层接口，Service层接口缺少保护性措施。
            Method batchSaveListMethod = bizBean.getClass().getMethod(excelOperator.getSaveMethodName(), List.class);
            Workbook workbook = WorkbookFactory.create(file.getInputStream());
            List list = ExcelHelper.parseExcel(file.getInputStream(), upperEntityName + "DTO", Class.forName(excelOperator.getDtoClassName()), 1);
            ResultDO saveResult = (ResultDO) batchSaveListMethod.invoke(bizBean, list);
            if (saveResult.isFailed()) {
                return ViewResultHelper.convertToViewResultByResultDO(saveResult, null);
            }
        } catch (EncryptedDocumentException e) {
            return ViewResultHelper.buildViewResultByServiceException(e, "上传文件不合法。");
        } catch (NoSuchMethodException e) {
            return ViewResultHelper.buildViewResultByServiceException(e, "无法找到规则下的方法资源。");
        } catch (Exception e) {
            if (e.getCause() != null && e.getCause() instanceof Exception) {
                return ViewResultHelper.buildViewResultByServiceException((Exception) e.getCause(), null);
            }
            logger.error(ServiceExceptionHelper.getExceptionInfo(e));
            return ViewResultHelper.buildViewResultByServiceException(e, e.getMessage());
        }
        return viewResultDTO;
    }

    @RequestMapping("/downloadTemplate")
    @ResponseBody
    public Object downloadTemplate(String entityName) {
        try {
            if (entityName == null || entityName.isEmpty()) {
                throw ServiceExceptionHelper.buildServiceException(ResultMessageEnum.PARAM_EMPTY, "entityName不能为空。");
            }
            // 定义模板文件存放的固定目录
            String templateDirectory = "templates" + File.separator; // resources下的templates目录
            String fullPath = templateDirectory + entityName + ".xlsx";
            // 使用 ClassPathResource 读取文件
            ClassPathResource resource = new ClassPathResource(fullPath);
            resource.getPath();
            // 读取文件内容
            byte[] fileContent = StreamUtils.copyToByteArray(resource.getInputStream());
            // 设置响应头
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            // 对文件名进行编码处理（解决中文乱码问题）
            String encodedFileName = URLEncoder.encode(entityName, StandardCharsets.UTF_8).replace("+", "%20");
            headers.set("Content-Disposition", "attachment; filename*=UTF-8''" + resource.getFilename());
            // 设置文件大小
            headers.setContentLength(fileContent.length);
            return new ResponseEntity<>(fileContent, headers, HttpStatus.OK);
        } catch (Exception e) {
            logger.error(ServiceExceptionHelper.getExceptionInfo(e));
            return gson.toJson(ViewResultHelper.buildViewResultByServiceException(e, null));
        }
    }


    private static ExcelOperator buildExcelOperator(Map<String, Object> params) {
        // TODO 此方法有严重的安全漏洞，正常，只允许设置entityName，其他属性都不应该设置，存在非常严重的安全漏洞。
        ExcelOperator excelOperator = new ExcelOperator();
        excelOperator.setEntityName((String) params.get("entityName"));
        excelOperator.setServiceBeanName((String) params.get("serviceBeanName"));
        excelOperator.setQueryClassName((String) params.get("queryClassName"));
        excelOperator.setDomainClassName((String) params.get("domainClassName"));
        excelOperator.setDtoClassName((String) params.get("dtoClassName"));
        String selectMethodName = (String) params.get("selectMethodName");
        if (selectMethodName != null && (selectMethodName.startsWith("select") || selectMethodName.startsWith("query"))) {
            // 一定程度处理安全漏洞，方法只能是select或者query开头。
            excelOperator.setSelectMethodName(selectMethodName);
        }
        excelOperator.setBizClassName((String) params.get("bizClassName"));

        String saveMethodName = (String) params.get("saveMethodName");
        if (saveMethodName != null && (saveMethodName.startsWith("batchSave") && selectMethodName.endsWith("List"))) {
            // 一定程度处理安全漏洞，方法只能是batchSave开头，并且List结尾。
            excelOperator.setSaveMethodName(saveMethodName);
        }
        initExcelOperator(excelOperator);
        return excelOperator;
    }

    private static ResultDO<List<?>> getResultDOByQuery(ExcelOperator excelOperator, Object queryBean, BeanFactory beanFactory) throws Exception {
        Class<?> queryClass = Class.forName(excelOperator.getQueryClassName());
        Object serviceBean = beanFactory.getBean(excelOperator.getServiceBeanName());
        Method method = serviceBean.getClass().getMethod(excelOperator.getSelectMethodName(), queryClass);
        method.setAccessible(true);
        return (ResultDO<List<?>>) method.invoke(serviceBean, queryBean);
    }

    /***
     * 默认从service层获取数据，当然也可以指定从BIZ层获取数据。
     * 二开时，需要考虑存在案例漏洞的风险，从两个方面入手：限死 【selectMethodName】名称，限定【entityName】范围。
     * 初始化的规则，可以调整
     * @param excelOperator 下载标识对象
     * */
    private static void initExcelOperator(ExcelOperator excelOperator) {

        if (excelOperator == null) {
            throw ServiceExceptionHelper.buildServiceException(ResultMessageEnum.PARAM_EMPTY, "下载标识对象为空。");
        } else if (excelOperator.getEntityName() == null || excelOperator.getEntityName().isEmpty()) {
            throw ServiceExceptionHelper.buildServiceException(ResultMessageEnum.PARAM_EMPTY, "entityName信息不能为空。");
        }
        String entityName = excelOperator.getEntityName();
        entityName = StringHelper.initUpper(entityName);
        String initLowerEntityName = StringHelper.initLower(entityName);
        if (excelOperator.getServiceBeanName() == null || excelOperator.getServiceBeanName().isEmpty()) {
            excelOperator.setServiceBeanName(initLowerEntityName + "Service");
        }

        if (excelOperator.getQueryClassName() == null || excelOperator.getQueryClassName().isEmpty()) {
            excelOperator.setQueryClassName(QUERY_BASE_PACKAGE + "Query" + entityName);
        }

        if (excelOperator.getDomainClassName() == null || excelOperator.getDomainClassName().isEmpty()) {
            excelOperator.setDomainClassName(DOMAIN_BASE_PACKAGE + entityName + "DO");
        }

        if (excelOperator.getDtoClassName() == null || excelOperator.getDtoClassName().isEmpty()) {
            excelOperator.setDtoClassName(DTO_BASE_PACKAGE + entityName + "DTO");
        }

        if (excelOperator.getSelectMethodName() == null || excelOperator.getSelectMethodName().isEmpty()) {
            excelOperator.setSelectMethodName("select" + entityName + "ListResult");
        }

        if (excelOperator.getBizClassName() == null || excelOperator.getBizClassName().isEmpty()) {
            excelOperator.setBizClassName(initLowerEntityName + "Biz");
        }

        if (excelOperator.getSaveMethodName() == null || excelOperator.getSaveMethodName().isEmpty()) {
            // 取自biz的批处理方法。batchSave[EntityName]List
            excelOperator.setSaveMethodName("batchSave" + entityName + "List");
        }

    }

    /**
     * 校验资源是否存在，主要是检查serviceBeanName、dtoClassName、queryClassName是否存在。
     *
     * @param beanFactory   spring bean工厂
     * @param excelOperator 下载标识对象
     * @throws ServiceException 资源不存在异常
     */
    private static void checkCommonExcelOperator(BeanFactory beanFactory, ExcelOperator excelOperator) {
        if (!beanFactory.containsBean(excelOperator.getServiceBeanName())) {
            throw ServiceExceptionHelper.buildServiceException(ResultMessageEnum.DATA_NOT_EXIST, "查询资源不存。访问资源entityName信息： " + excelOperator.getEntityName());
        } else if (!beanFactory.containsBean(excelOperator.getServiceBeanName())) {
            throw ServiceExceptionHelper.buildServiceException(ResultMessageEnum.DATA_NOT_EXIST, "查询参数类不存在。访问资源serviceBeanName信息： " + excelOperator.getServiceBeanName());
        }
    }

    private static void checkDownloadExcelOperator(BeanFactory beanFactory, ExcelOperator excelOperator) {
        try {
            Class.forName(excelOperator.getDtoClassName());
            Class.forName(excelOperator.getQueryClassName());
        } catch (ClassNotFoundException e) {
            throw ServiceExceptionHelper.buildServiceException(ResultMessageEnum.DATA_NOT_EXIST, "DTO或查询参数类不存在。访问资源dtoClassName或queryClassName信息： " + excelOperator.getDtoClassName() + " 或 " + excelOperator.getQueryClassName());
        }
    }

    private static void checkUploadExcelOperator(BeanFactory beanFactory, ExcelOperator excelOperator) {
        try {
            beanFactory.getBean(excelOperator.getBizClassName());
        } catch (BeansException e) {
            throw ServiceExceptionHelper.buildServiceException(ResultMessageEnum.DATA_NOT_EXIST, "Biz或服务资源不存在。访问资源信息： " + excelOperator.getBizClassName());
        }
    }


    private static String buildDownloadExcelFileName(ExcelOperator excelOperator) {
        String currentTime = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(new Date());
        return "Data-" + excelOperator.getEntityName() + "_" + currentTime + ".xlsx";
    }
}
</#noparse>